/*
    SUSAN® - Sole of Unix Save ANything

   
      Copyright (C) 2011-2016 Skyatlas Co. LTD

   

   
*/
/*
 * Kern Sibbald, March MM
 */
/**
 * @file
 * Susan File Daemon
 */

#include "include/susan.h"
#include "filed/filed.h"
#include "filed/filed_globals.h"
#include "filed/dir_cmd.h"
#include "filed/socket_server.h"
#include "lib/mntent_cache.h"
#include "lib/daemon.h"
#include "lib/bnet_network_dump.h"
#include "lib/bsignal.h"
#include "lib/parse_conf.h"
#include "lib/watchdog.h"
#include "lib/util.h"
#include "lib/address_conf.h"

using namespace filedaemon;

/* Imported Functions */
extern void* handle_connection_request(void* dir_sock);
extern bool PrintMessage(void* sock, const char* fmt, ...);

/* Forward referenced functions */
static bool CheckResources();

static bool foreground = false;

static void usage()
{
  kSusanVersionStrings.PrintCopyrightWithFsfAndPlanets(stderr, 2000);
  fprintf(
      stderr,
      _("Usage: susan-fd [options]\n"
        "        -b          backup only mode\n"
        "        -c <path>   use <path> as configuration file or directory\n"
        "        -d <nn>     set debug level to <nn>\n"
        "        -dt         print timestamp in debug output\n"
        "        -f          run in foreground (for debugging)\n"
        "        -g <group>  run as group <group>\n"
        "        -k          keep readall capabilities\n"
        "        -m          print kaboom output (for debugging)\n"
        "        -r          restore only mode\n"
        "        -s          no signals (for debugging)\n"
        "        -t          test configuration file and exit\n"
        "        -u <user>   run as user <user>\n"
        "        -v          verbose user messages\n"
        "        -xc         print configuration and exit\n"
        "        -xs         print configuration file schema in JSON format "
        "and exit\n"
        "        -?          print this message.\n"
        "\n"));

  exit(1);
}


/*********************************************************************
 *
 *  Main Susan Unix Client Program
 *
 */
#if defined(HAVE_WIN32)
#define main SusanMain
#endif

int main(int argc, char* argv[])
{
  int ch;
  bool test_config = false;
  bool export_config = false;
  bool export_config_schema = false;
  bool keep_readall_caps = false;
  char* uid = NULL;
  char* gid = NULL;

  setlocale(LC_ALL, "");
  tzset();
  bindtextdomain("susan", LOCALEDIR);
  textdomain("susan");

  InitStackDump();
  MyNameIs(argc, argv, "susan-fd");
  InitMsg(NULL, NULL);
  daemon_start_time = time(NULL);

  while ((ch = getopt(argc, argv, "bc:d:fg:kmrstu:vx:z:?")) != -1) {
    switch (ch) {
      case 'b':
        backup_only_mode = true;
        break;

      case 'c': /* configuration file */
        if (configfile != NULL) { free(configfile); }
        configfile = strdup(optarg);
        break;

      case 'd': /* debug level */
        if (*optarg == 't') {
          dbg_timestamp = true;
        } else {
          debug_level = atoi(optarg);
          if (debug_level <= 0) { debug_level = 1; }
        }
        break;

      case 'f': /* run in foreground */
        foreground = true;
        break;

      case 'g': /* set group */
        gid = optarg;
        break;

      case 'k':
        keep_readall_caps = true;
        break;

      case 'm': /* print kaboom output */
        prt_kaboom = true;
        break;

      case 'r':
        restore_only_mode = true;
        break;

      case 's':
        no_signals = true;
        break;

      case 't':
        test_config = true;
        break;

      case 'u': /* set userid */
        uid = optarg;
        break;

      case 'v': /* verbose */
        verbose++;
        break;

      case 'x': /* export configuration/schema and exit */
        if (*optarg == 's') {
          export_config_schema = true;
        } else if (*optarg == 'c') {
          export_config = true;
        } else {
          usage();
        }
        break;

      case 'z': /* switch network debugging on */
        if (!BnetDump::EvaluateCommandLineArgs(optarg)) { exit(1); }
        break;

      case '?':
      default:
        usage();
        break;
    }
  }
  argc -= optind;
  argv += optind;

  if (argc) {
    if (configfile != NULL) free(configfile);
    configfile = strdup(*argv);
    argc--;
    argv++;
  }
  if (argc) { usage(); }

  if (!uid && keep_readall_caps) {
    Emsg0(M_ERROR_TERM, 0, _("-k option has no meaning without -u option.\n"));
  }

  /*
   * See if we want to drop privs.
   */
  if (geteuid() == 0) { drop(uid, gid, keep_readall_caps); }

  if (!no_signals) {
    InitSignals(TerminateFiled);
  } else {
    /*
     * This reduces the number of signals facilitating debugging
     */
    watchdog_sleep_time = 120; /* long timeout for debugging */
  }

  if (export_config_schema) {
    PoolMem buffer;

    my_config = InitFdConfig(configfile, M_ERROR_TERM);
    PrintConfigSchemaJson(buffer);
    printf("%s\n", buffer.c_str());
    goto bail_out;
  }

  my_config = InitFdConfig(configfile, M_ERROR_TERM);
  my_config->ParseConfig();

  if (export_config) {
    my_config->DumpResources(PrintMessage, NULL);
    goto bail_out;
  }

  if (!foreground && !test_config) {
    daemon_start();
    InitStackDump(); /* set new pid */
  }

  if (InitCrypto() != 0) {
    Emsg0(M_ERROR, 0, _("Cryptography library initialization failed.\n"));
    TerminateFiled(1);
  }

  if (!CheckResources()) {
    Emsg1(M_ERROR, 0, _("Please correct configuration file: %s\n"),
          my_config->get_base_config_path().c_str());
    TerminateFiled(1);
  }

  SetWorkingDirectory(me->working_directory);

#if defined(HAVE_WIN32)
  if (me->compatible) {
    Win32SetCompatible();
  } else {
    Win32ClearCompatible();
  }
#endif

  if (test_config) {
    if (my_config->HasWarnings()) {
      /* messaging not initialized, so Jmsg with  M_WARNING doesn't work */
      fprintf(stderr, _("There are configuration warnings:\n"));
      for (auto& warning : my_config->GetWarnings()) {
        fprintf(stderr, " * %s\n", warning.c_str());
      }
    }
    TerminateFiled(0);
  }

  /* Maximum 1 daemon at a time */
  CreatePidFile(me->pid_directory, "susan-fd",
                GetFirstPortHostOrder(me->FDaddrs));
  ReadStateFile(me->working_directory, "susan-fd",
                GetFirstPortHostOrder(me->FDaddrs));

  LoadFdPlugins(me->plugin_directory, me->plugin_names);

  InitJcrChain();
  if (!no_signals) {
    StartWatchdog(); /* start watchdog thread */
    if (me->jcr_watchdog_time) {
      InitJcrSubsystem(
          me->jcr_watchdog_time); /* start JobControlRecord watchdogs etc. */
    }
  }

  /*
   * if configured, start threads and connect to Director.
   */
  StartConnectToDirectorThreads();

  /*
   * start socket server to listen for new connections.
   */
  StartSocketServer(me->FDaddrs);

  TerminateFiled(0);

bail_out:
  exit(0);
}

namespace filedaemon {

void TerminateFiled(int sig)
{
  static bool already_here = false;

  if (already_here) {
    Bmicrosleep(2, 0); /* yield */
    exit(1);           /* prevent loops */
  }
  already_here = true;
  debug_level = 0; /* turn off debug */
  StopWatchdog();

  StopConnectToDirectorThreads(true);
  StopSocketServer(true);

  UnloadFdPlugins();
  FlushMntentCache();
  WriteStateFile(me->working_directory, "susan-fd",
                 GetFirstPortHostOrder(me->FDaddrs));
  DeletePidFile(me->pid_directory, "susan-fd",
                GetFirstPortHostOrder(me->FDaddrs));

  if (configfile != NULL) { free(configfile); }

  if (debug_level > 0) { PrintMemoryPoolStats(); }
  if (my_config) {
    delete my_config;
    my_config = NULL;
  }
  TermMsg();
  CleanupCrypto();
  CloseMemoryPool(); /* release free memory in pool */
  exit(sig);
}
} /* namespace filedaemon */

/**
 * Make a quick check to see that we have all the
 * resources needed.
 */
static bool CheckResources()
{
  bool OK = true;
  DirectorResource* director;
  const std::string& configfile = my_config->get_base_config_path();

  LockRes(my_config);

  me = (ClientResource*)my_config->GetNextRes(R_CLIENT, NULL);
  my_config->own_resource_ = me;
  if (!me) {
    Emsg1(M_FATAL, 0,
          _("No File daemon resource defined in %s\n"
            "Without that I don't know who I am :-(\n"),
          configfile.c_str());
    OK = false;
  } else {
    /*
     * Sanity check.
     */
    if (me->MaxConnections < (2 * me->MaxConcurrentJobs)) {
      me->MaxConnections = (2 * me->MaxConcurrentJobs) + 2;
    }

    if (my_config->GetNextRes(R_CLIENT, (SusanResource*)me) != NULL) {
      Emsg1(M_FATAL, 0, _("Only one Client resource permitted in %s\n"),
            configfile.c_str());
      OK = false;
    }
    MyNameIs(0, NULL, me->resource_name_);
    if (!me->messages) {
      me->messages = (MessagesResource*)my_config->GetNextRes(R_MSGS, NULL);
      if (!me->messages) {
        Emsg1(M_FATAL, 0, _("No Messages resource defined in %s\n"),
              configfile.c_str());
        OK = false;
      }
    }
    if (me->pki_encrypt || me->pki_sign) {
#ifndef HAVE_CRYPTO
      Jmsg(NULL, M_FATAL, 0,
           _("PKI encryption/signing enabled but not compiled into Susan.\n"));
      OK = false;
#endif
    }

    /* pki_encrypt implies pki_sign */
    if (me->pki_encrypt) { me->pki_sign = true; }

    if ((me->pki_encrypt || me->pki_sign) && !me->pki_keypair_file) {
      Emsg2(M_FATAL, 0,
            _("\"PKI Key Pair\" must be defined for File"
              " daemon \"%s\" in %s if either \"PKI Sign\" or"
              " \"PKI Encrypt\" are enabled.\n"),
            me->resource_name_, configfile.c_str());
      OK = false;
    }

    /* If everything is well, attempt to initialize our public/private keys */
    if (OK && (me->pki_encrypt || me->pki_sign)) {
      char* filepath = nullptr;
      /* Load our keypair */
      me->pki_keypair = crypto_keypair_new();
      if (!me->pki_keypair) {
        Emsg0(M_FATAL, 0, _("Failed to allocate a new keypair object.\n"));
        OK = false;
      } else {
        if (!CryptoKeypairLoadCert(me->pki_keypair, me->pki_keypair_file)) {
          Emsg2(M_FATAL, 0,
                _("Failed to load public certificate for File"
                  " daemon \"%s\" in %s.\n"),
                me->resource_name_, configfile.c_str());
          OK = false;
        }

        if (!CryptoKeypairLoadKey(me->pki_keypair, me->pki_keypair_file, NULL,
                                  NULL)) {
          Emsg2(M_FATAL, 0,
                _("Failed to load private key for File"
                  " daemon \"%s\" in %s.\n"),
                me->resource_name_, configfile.c_str());
          OK = false;
        }
      }

      /*
       * Trusted Signers. We're always trusted.
       */
      me->pki_signers = new alist(10, not_owned_by_alist);
      if (me->pki_keypair) {
        me->pki_signers->append(crypto_keypair_dup(me->pki_keypair));
      }

      /* If additional signing public keys have been specified, load them up */
      if (me->pki_signing_key_files) {
        foreach_alist (filepath, me->pki_signing_key_files) {
          X509_KEYPAIR* keypair;

          keypair = crypto_keypair_new();
          if (!keypair) {
            Emsg0(M_FATAL, 0, _("Failed to allocate a new keypair object.\n"));
            OK = false;
          } else {
            if (CryptoKeypairLoadCert(keypair, filepath)) {
              me->pki_signers->append(keypair);

              /* Attempt to load a private key, if available */
              if (CryptoKeypairHasKey(filepath)) {
                if (!CryptoKeypairLoadKey(keypair, filepath, NULL, NULL)) {
                  Emsg3(M_FATAL, 0,
                        _("Failed to load private key from file %s for File"
                          " daemon \"%s\" in %s.\n"),
                        filepath, me->resource_name_, configfile.c_str());
                  OK = false;
                }
              }

            } else {
              Emsg3(M_FATAL, 0,
                    _("Failed to load trusted signer certificate"
                      " from file %s for File daemon \"%s\" in %s.\n"),
                    filepath, me->resource_name_, configfile.c_str());
              OK = false;
            }
          }
        }
      }

      /*
       * Crypto recipients. We're always included as a recipient.
       * The symmetric session key will be encrypted for each of these readers.
       */
      me->pki_recipients = new alist(10, not_owned_by_alist);
      if (me->pki_keypair) {
        me->pki_recipients->append(crypto_keypair_dup(me->pki_keypair));
      }


      /* If additional keys have been specified, load them up */
      if (me->pki_master_key_files) {
        foreach_alist (filepath, me->pki_master_key_files) {
          X509_KEYPAIR* keypair;

          keypair = crypto_keypair_new();
          if (!keypair) {
            Emsg0(M_FATAL, 0, _("Failed to allocate a new keypair object.\n"));
            OK = false;
          } else {
            if (CryptoKeypairLoadCert(keypair, filepath)) {
              me->pki_recipients->append(keypair);
            } else {
              Emsg3(M_FATAL, 0,
                    _("Failed to load master key certificate"
                      " from file %s for File daemon \"%s\" in %s.\n"),
                    filepath, me->resource_name_, configfile.c_str());
              OK = false;
            }
          }
        }
      }
    }
  }


  /* Verify that a director record exists */
  LockRes(my_config);
  director = (DirectorResource*)my_config->GetNextRes(R_DIRECTOR, NULL);
  UnlockRes(my_config);
  if (!director) {
    Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"),
          configfile.c_str());
    OK = false;
  }

  UnlockRes(my_config);

  if (OK) {
    CloseMsg(NULL);              /* close temp message handler */
    InitMsg(NULL, me->messages); /* open user specified message handler */
    if (me->secure_erase_cmdline) {
      SetSecureEraseCmdline(me->secure_erase_cmdline);
    }
    if (me->log_timestamp_format) {
      SetLogTimestampFormat(me->log_timestamp_format);
    }
  }

  return OK;
}
